﻿using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Reflection;
using System.ComponentModel;

namespace ArgusLib.InteropServices
{
	public abstract class ArrayMarshaler<T> : ICustomMarshaler
	{
		T arrayEnd;

		public ArrayMarshaler(T arrayEnd)
		{
			this.arrayEnd = arrayEnd;
		}

		/// <summary>
		/// Implementations should marshal one array element.
		/// </summary>
		protected abstract T MarshalElement(IntPtr native);

		/// <summary>
		/// Implementations should return the size in bytes of
		/// the array element.
		/// </summary>
		protected abstract int SizeOf(T element);

		protected abstract void MarshalElement(IntPtr native, T element);

		public object MarshalNativeToManaged(IntPtr native)
		{
			if (native == IntPtr.Zero)
				return null;
			List<T> RetVal = new List<T>();
			T element = this.MarshalElement(native);
			IntPtr adress = native;
			while (element.Equals(this.arrayEnd) == false)
			{
				RetVal.Add(element);
				adress = new IntPtr(adress.ToInt64() + this.SizeOf(element));
				element = this.MarshalElement(adress);
			}
			return RetVal.ToArray();
		}

		public IntPtr MarshalManagedToNative(object managed)
		{
			T[] array = managed as T[];
			if (array == null)
				return IntPtr.Zero;

			int totalSize = 0;
			foreach (T element in array)
			{
				totalSize += this.SizeOf(element);
			}
			totalSize += this.SizeOf(this.arrayEnd);

			IntPtr RetVal = Marshal.AllocHGlobal(totalSize);
			IntPtr adress = RetVal;

			foreach (T element in array)
			{
				this.MarshalElement(adress, element);
				adress = new IntPtr(adress.ToInt64() + this.SizeOf(element));
			}
			this.MarshalElement(adress, this.arrayEnd);
			return RetVal;
		}

		public void CleanUpNativeData(IntPtr native)
		{
			Marshal.FreeHGlobal(native);
		}

		public void CleanUpManagedData(object managed)
		{
			IDisposable disposable = managed as IDisposable;
			if (disposable != null)
				disposable.Dispose();
		}

		public int GetNativeDataSize()
		{
			return -1;
		}
	}

	public class StructArrayMarshaller<T> : ArrayMarshaler<T>, ICustomMarshaler
	{
		public StructArrayMarshaller(T arrayEnd)
			: base(arrayEnd)
		{
			Type type = typeof(T);
			if (type.IsValueType == false)
			{
				object[] attr = type.GetCustomAttributes(typeof(StructLayoutAttribute), false);
				if (attr == null || attr.Length < 1)
				{
					throw new GenericTypeParameterNotSupportetException(
						"The type must contain layout information. Apply StructLayoutAttribute.");
				}
			}
		}

		public static ICustomMarshaler GetInstance(string cookie)
		{
			TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
			if (converter.CanConvertFrom(typeof(string)) == false)
				return null;

			T arrayEnd = (T)converter.ConvertFromInvariantString(cookie);
			return new StructArrayMarshaller<T>(arrayEnd);
		}

		protected override int SizeOf(T element)
		{
			return Marshal.SizeOf(element);
		}

		protected override void MarshalElement(IntPtr native, T element)
		{
			Marshal.StructureToPtr(element, native, false);
		}

		protected override T MarshalElement(IntPtr native)
		{
			return (T)Marshal.PtrToStructure(native, typeof(T));
		}
	}
}
